﻿namespace DCHealthCheck.DomainModel {

    #region Usings
    using DCHealthCheck.Common;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Diagnostics;
    using System.IO;
    using System.IO.Compression;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks; 
    #endregion

    [Table("Audit")]
    public class Audit : IAudit, IObjectWithState, IValidatableObject {

        #region Members
        public virtual byte[] AuditEntry {
            get { return auditEntry; }
            set {
                auditEntry = value;
                this.UnCompressAuditData();
            }
        }
        private byte[] auditEntry;

        public virtual DateTime DateTimeUTC { get; set; }

        [Key]
        public virtual int Id { get; set; }

        public virtual string Username { get; set; }

        private static readonly int UsernameMaxLength = 255;

        #region NotMapped properties
        [NotMapped]
        public virtual string AuditEntryText { get; set; }

        [NotMapped]
        public DetachedState DetachedState { get; set; }

        [NotMapped]
        public Object EntityKey { get { return this.Id; } }

        [NotMapped]
        public Dictionary<string, object> StartingOriginalValues { get; set; }
        #endregion 
        #endregion

        #region Constructor
        public Audit() {
            this.AuditEntryText = string.Empty;
            this.Username = string.Empty;
        } 
        #endregion

        #region Methods
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
        public void CompressAuditData() {
            if (!string.IsNullOrWhiteSpace(this.AuditEntryText)) {
                byte[] uncompressedLogData = Encoding.UTF8.GetBytes(this.AuditEntryText);
                using (MemoryStream compressedMemoryStream = new MemoryStream()) {
                    using (DeflateStream deflateStream = new DeflateStream(compressedMemoryStream, CompressionMode.Compress, true)) {
                        deflateStream.Write(uncompressedLogData, 0, uncompressedLogData.Length);
                    }

                    this.AuditEntry = compressedMemoryStream.ToArray();
                }
            }
        }
        
        [DebuggerStepThroughAttribute]
        public override string ToString() {
            var info = new StringBuilder();
            info.Append("Audit: ");
            info.AppendFormat("DateTimeUTC: {0}; ", this.DateTimeUTC.ShortDateFriendly());
            info.AppendFormat("Username: {0}", this.Username);

            return info.ToString();
        }

        [DebuggerStepThroughAttribute]
        public string ToVerboseString() {
            var info = new StringBuilder();
            info.AppendFormat("DateTimeUTC: {0}; ", this.DateTimeUTC.ShortDateFriendly());
            info.AppendFormat("Subject: {0}; ", this.Username);
            info.AppendFormat("Audit Entry: {0} ", this.AuditEntryText);

            return info.ToString();
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
        public void UnCompressAuditData() {
            if ((this.AuditEntry != null) && (this.AuditEntry.Length > 0)) {
                using (MemoryStream memoryStreamInput = new MemoryStream(this.AuditEntry))
                using (MemoryStream memoryStreamOutput = new MemoryStream()) {
                    using (DeflateStream deflateStream = new DeflateStream(memoryStreamInput, CompressionMode.Decompress)) {
                        deflateStream.CopyTo(memoryStreamOutput);
                    }

                    this.AuditEntryText = Encoding.UTF8.GetString(memoryStreamOutput.ToArray());
                }
            }
        }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
            if (this.DateTimeUTC == DateTime.MinValue) {
                yield return new ValidationResult(
                    "DateTimeUTC is required.", new[] { "DateTimeUTC" });
            }

            if (string.IsNullOrWhiteSpace(this.Username)) {
                yield return new ValidationResult(
                    "Username is required.", new[] { "Username" });
            }
            else if (this.Username.Length > UsernameMaxLength) {
                yield return new ValidationResult(string.Format("Username length: {0} exceeds max length: {1}",
                    this.Username.Length, UsernameMaxLength), new[] { "Username" });
            }

            if ((this.AuditEntry == null) || (this.AuditEntry.Length == 0)) {
                yield return new ValidationResult(
                    "AuditEntry is required.", new[] { "AuditEntry" });
            }
        }  
        #endregion
    }
}
